package stdio.costa.imoca.spi;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Scope;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import stdio.costa.imoca.model.Image;
import stdio.costa.imoca.utils.ImageUtils;

import com.qiniu.qbox.auth.DigestAuthClient;
import com.qiniu.qbox.rs.PublishRet;
import com.qiniu.qbox.rs.PutAuthRet;
import com.qiniu.qbox.rs.PutFileRet;
import com.qiniu.qbox.rs.RSClient;
import com.qiniu.qbox.rs.RSService;

/**
 * 这个类的使用前提是，七牛的存储端已经人工创建了bucket, 所以程序里没有调用创建bucket的代码。把运维的工作从程序中分离出去。
 * @author 张桂林
 *
 */
@Service
@Scope(BeanDefinition.SCOPE_SINGLETON)
public class QiniuFileServiceAdapter implements ImageServiceSpi {

	private static final Logger logger = LoggerFactory.getLogger(QiniuFileServiceAdapter.class);

	@Value("#{properties['qiniu.bucket.name']}")
	private String bucketName;

	@Value("#{properties['qiniu.publish.domain']}")
	private String publishDomain;
	
	@Value("#{properties['image.width.medium']}")
	private int mediumWidth;
	
	@Value("#{properties['image.height.medium']}")
	private int mediumHeight;
	
	@Value("#{properties['image.width.thumbnail']}")
	private int thumbnailWidth;
	
	@Value("#{properties['image.height.thumbnail']}")
	private int thumbnailHeight;
	
	@Value("#{properties['image.width.large']}")
	private int largeWidth;
	
	@Value("#{properties['image.height.large']}")
	private int largeHeight;

	@PersistenceContext
	private EntityManager entityManager;
	
	@Override
	@Async
	@Transactional
	public void create(Image image) {
		RSService service = new RSService(new DigestAuthClient(), bucketName);
		PutAuthRet putAuthRet = service.putAuth();
		if (putAuthRet.ok()) {
			Map<String, String> callbackParams = buildCallbackParams(image.getUuid());
			PutFileRet putFileRet = upload(image, putAuthRet, callbackParams);
			if (putFileRet.ok()) {
				PublishRet publishRet = service.publish(publishDomain);
				if (publishRet.ok()) {
					String url = getUrl(image.getUuid());
					image.setLargeUrl(getUrl(url, largeWidth, largeHeight));
					image.setMediumUrl(getUrl(url, mediumWidth, mediumHeight));
					image.setThumbnailUrl(getUrl(url, thumbnailWidth, thumbnailHeight));
					entityManager.merge(image);
				}
				logger.error("Failed of publish", publishRet.exception);
			}
			logger.error("Failed of putFile", putFileRet.exception);
		}
		logger.error("Failed of putAuth", putAuthRet.exception);
	}

	private String getUrl(String uuid) {
		return new StringBuilder(publishDomain).append(uuid).toString();
	}
	
	private String getUrl(String baseUrl, int width, int height) {
		return new StringBuilder(baseUrl).append("?imageView/1/w/").append(width).append("/h/").append(height).toString();
	}

	protected PutFileRet upload(Image image, PutAuthRet putAuthRet, Map<String, String> callbackParams) {
		try {
			byte[] data = ImageUtils.zoom(image.getData(), largeWidth, largeHeight);
			return RSClient.putFile(putAuthRet.getUrl(), bucketName, image.getUuid(), image.getMediaType(), new ByteArrayInputStream(data), null, callbackParams);
		} catch(IOException e) {
			logger.error("Can not upload image", e);
			throw new IllegalStateException(e);
		}
	}

	@Override
	public InputStream get(String uuid, Integer width, Integer height) {
		try {
			String url = getUrl(uuid);
			InputStream in = new URL(getUrl(url, width, height)).openStream();
			byte[] data = ImageUtils.zoom(in, width, height);
			return new ByteArrayInputStream(data);
		} catch (MalformedURLException e) {
			throw new IllegalStateException("URL format is wrong, please check system configuration.");
		} catch (IOException e) {
			logger.error("Failed to open stream.", e);
			return null;
		}
	}

	protected Map<String, String> buildCallbackParams(String uuid) {
		HashMap<String, String> callbackParams = new HashMap<String, String>();
		callbackParams.put("key", uuid);
		return callbackParams;
	}
}
